﻿#region MIT Licenze
//The MIT License

//Copyright (c) 2008 Sebastian Faltoni <sebastian {at} dotnetfireball.org>

//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:

//The above copyright notice and this permission notice shall be included in
//all copies or substantial portions of the Software.

//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//THE SOFTWARE.
#endregion

namespace Fireball.Core.Collections
{
    #region Usings
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Runtime.InteropServices;
    using System.Collections.Specialized;
    using System.ComponentModel; 
    #endregion

    #region KeyedValue
    [Serializable, StructLayout(LayoutKind.Sequential)]
    public struct KeyedValue<T>
    {
        public string Key { get; set; }
        public T Value { get; set; }

        public override string ToString()
        {
            StringBuilder builder = new StringBuilder();
            builder.Append('[');
            if (this.Key != null)
            {
                builder.Append(this.Key.ToString());
            }
            builder.Append(", ");
            if (this.Value != null)
            {
                builder.Append(this.Value.ToString());
            }
            builder.Append(']');
            return builder.ToString();
        }
    } 
    #endregion

    #region KeyedCollection
    public class KeyedCollection<T> : System.Collections.ObjectModel.KeyedCollection<string, KeyedValue<T>>
  , INotifyCollectionChanged, INotifyPropertyChanged
    {
        #region Monitor
        [Serializable]
        private class SimpleMonitor : IDisposable
        {
            // Fields
            private int _busyCount;

            // Methods
            public void Dispose()
            {
                this._busyCount--;
            }

            public void Enter()
            {
                this._busyCount++;
            }

            // Properties
            public bool Busy
            {
                get
                {
                    return (this._busyCount > 0);
                }
            }
        }

        private SimpleMonitor Monitor
        { get; set; }
        #endregion

        #region Add
        public void Add(string key, T value)
        {
            this.Add(new KeyedValue<T>() { Key = key, Value = value });
        } 
        #endregion

        #region InsertItem
        protected override void InsertItem(int index, KeyedValue<T> item)
        {
            this.CheckReentrancy();
            base.InsertItem(index, item);
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(
                NotifyCollectionChangedAction.Add, item, index));
            this.OnPropertyChanged("Count");
            this.OnPropertyChanged("Item[]");            
        }
        #endregion

        #region ClearItems
        protected override void ClearItems()
        {
            this.CheckReentrancy();
            base.ClearItems();
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(
                NotifyCollectionChangedAction.Reset, null));
            this.OnPropertyChanged("Count");
            this.OnPropertyChanged("Item[]");
        }
        #endregion

        #region RemoveItem
        protected override void RemoveItem(int index)
        {
            this.CheckReentrancy();
            base.RemoveItem(index);
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(
                NotifyCollectionChangedAction.Remove, null, index));
            this.OnPropertyChanged("Count");
            this.OnPropertyChanged("Item[]");
        }
        #endregion

        #region SetItem
        protected override void SetItem(int index, KeyedValue<T> item)
        {
            this.CheckReentrancy();
            base.SetItem(index, item);
            this.OnCollectionChanged(new NotifyCollectionChangedEventArgs(
                NotifyCollectionChangedAction.Replace, item, index));
            this.OnPropertyChanged("Count");
            this.OnPropertyChanged("Item[]");
        }
        #endregion

        #region GetKeyItem
        protected override string GetKeyForItem(KeyedValue<T> item)
        {
            return item.Key;
        }
        #endregion

        #region Monitor Methods
        protected IDisposable BlockReentrancy()
        {
            this.Monitor.Enter();
            return this.Monitor;
        }

        protected void CheckReentrancy()
        {
            if ((this.Monitor.Busy && (this.CollectionChanged != null)) && (this.CollectionChanged.GetInvocationList().Length > 1))
            {
                throw new InvalidOperationException("ObservableCollectionReentrancyNotAllowed");
            }
        }
        #endregion

        #region Changes Methods
        protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (this.CollectionChanged != null)
            {
                using (this.BlockReentrancy())
                {
                    this.CollectionChanged(this, e);
                }
            }
        }

        private void OnPropertyChanged(string propertyName)
        {
            this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }

        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, e);
            }
        }
        #endregion

        #region INotifyCollectionChanged Members

        public event NotifyCollectionChangedEventHandler CollectionChanged;

        #endregion

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    } 
    #endregion
}
